﻿#include "Sistema.h"
#include "Ciudad.h"
#include "Excursion.h"
#include "Atraccion.h"
#include "ComparadorClave.h"
#include "ComparadorNat.h"
#include "FuncionHash.h" 
#include "NaturalFuncionHash.h" 
#include "CadenaFuncionHash.h"
#include "ComparadorCiudadesPorNombre.h"
#include "ComparadorCadena.h"
#include "FuncionHashCuidad.h"
#include "ComparadorCiudadesPorPromedio.h"
#include "ColaPrioridadImp.h"
#include "FuncionHashCiudadesPromedio.h"
#include "ComparadorCiudades.h"
#include "Excursion.h"
#include "ComparadorNat.h"
#include "NaturalFuncionHash.h"

Sistema::Sistema(nat VELOCIDAD_OMNIBUS, nat VELOCIDAD_AVION, nat COSTO_OMNIBUS, nat COSTO_AVION, nat CAPACIDAD_OMNIBUS, nat CAPACIDAD_AVION, nat MAX_CIUDADES, nat MAX_EXCURSIONES)
{
	this->maxCiudades = MAX_CIUDADES;
	this->maxExcursiones = MAX_EXCURSIONES;
	
	this->cantCiudades = 0;
	this->cantExcursiones = 0;

	this->costoOmnibus = COSTO_OMNIBUS;
	this->costoAvion = COSTO_AVION;

	this->velocidadOmnibus = VELOCIDAD_OMNIBUS;
	this->velocidadAvion = VELOCIDAD_AVION;

	this->capacidadOmnibus = CAPACIDAD_OMNIBUS;
	this->capacidadAvion = CAPACIDAD_AVION;

	this->avlCiudades = new AVL<Puntero<ICiudad>>(new ComparadorCiudadesPorNombre());
		
	Puntero<FuncionHash<Cadena>> funcionCadena= new CadenaFuncionHash();
	
	this->matrizAtraccion = Matriz<Puntero<IAtraccion>>(26,100);
	this->initMatrizAtraccion();
	
	this->grafoCiudad = new GrafoCiudad(MAX_CIUDADES, new ComparadorCiudadesPorNombre());
	this->tablaCiudades = new TablaHash<Cadena, Puntero<ICiudad>>(new ComparadorCadena(), new CadenaFuncionHash(), MAX_CIUDADES);

	this->tablaExcursion = new TablaHash<nat, Puntero<IExcursion>>(new ComparadorNat(), new NaturalFuncionHash(), MAX_EXCURSIONES);

	Puntero<FuncionHash<Puntero<Ciudad>>> funcionHashCiudad = new FuncionHashCiudadesPromedio();
	Puntero<Comparador<Puntero<Ciudad>>> comparadorCiudadPorPromedio = new ComparadorCiudadesPorPromedio();
	//Puntero<ColaDePrioridad<Tupla<nat,Cadena>>> aldeasCP = new ColaDePrioridadImp<Tupla<nat,Cadena>>(compClave,new FHashAldea(),this->maxAldeas,compClaveHash);

	//Puntero<ColaDePrioridad<Puntero<ICiudad>>> heap = new ColaDePrioridadImp<Puntero<ICiudad>>(comparadorCiudadPorPromedio,funcionCadena,MAX_CIUDADES + 1,new ComparadorCadena());
	//this->colaPaisesConMasPuntaje = new ColaPrioridadImp<Puntero<Pais>>(maxPaisesAux,fHashPais,fIgualesPaises,ptrComparadorPais2);

	this->heapCiudades = new ColaPrioridadImp<Puntero<Ciudad>>(MAX_CIUDADES + 1,funcionHashCiudad,comparadorCiudadPorPromedio, new ComparadorCiudades());

	//this->tablaExcursiones = new TablaHash<nat, Puntero<IExcursion>>(new ComparadorNat(), new NaturalFuncionHash(), MAX_EXCURSIONES);
}

Sistema::~Sistema()
{
}
// Hito de Control Tipo 1

TipoRetorno Sistema::IngresoCiudad(Cadena nombre)
{
	// Sobrepasa la cantidad maxima de ciudades permitidas
	if(this->cantCiudades == this->maxCiudades){
		return ERROR;
	}
		
	// controlo que no exista una ciudad con el mismo nombre
	if (this->tablaCiudades->EstaDefinido(nombre)){
		cout << "Ya existe un ciudad con ese nombre" << endl;
		return ERROR;		
	}
	
	//Estoy en condiciones de insertar la nueva ciudad
	Puntero<ICiudad> ciudad = new Ciudad(nombre);
	this->avlCiudades->Insertar(ciudad);
	// Tambien en tabla para O(1) prom al obtener
	this->tablaCiudades->Insertar(nombre, ciudad);

	this->cantCiudades++;

	return OK;
}

TipoRetorno Sistema::IngresoAtraccion(Cadena identificador, Cadena nombre, nat horarioInicio, nat horarioFin, Cadena nombreCiudad, nat precio)
{
	Tupla<nat,nat> tupla = ObtenerPosiciones(identificador);

	//Ya esta ingresado ese codigo de Atraccion
	if(matrizAtraccion[tupla.Dato1][tupla.Dato2] != NULL)
	{
		cout << "Ya se encuentra registrado el codigo: " << identificador << endl;
		return ERROR;
	}

	if(horarioInicio >= horarioFin)
	{
		cout << "Horario Inicio no puede ser mayor o igual al Horario Fin: " << endl;
		return ERROR;
	
	}
	
	if (!this->tablaCiudades->EstaDefinido(nombreCiudad)){
		cout << "No existe una ciudad con nombre: " << nombreCiudad << endl;
		return ERROR;			
	}
	
	if(precio < 0)
	{	
		cout << "Precio no puede ser negativo: " << endl;
		return ERROR;			
	}
	
	//Recupero la ciudad del hash
	Puntero<ICiudad> pCiudad = this->tablaCiudades->Recuperar(nombreCiudad);

	Puntero<IAtraccion> atraccion = new Atraccion(identificador, nombre, horarioInicio, horarioFin, pCiudad,  precio);
	
	this->matrizAtraccion[tupla.Dato1][tupla.Dato2] = atraccion;
	
	return OK;


}

TipoRetorno Sistema::CalificarAtraccion(nat fechaCalificacion, Cadena idAtraccion, int calificacion, Cadena comentarios)
{	
	//Calificacion en rango invalido
	if(calificacion<-5 || calificacion >5 )
	{
		cout << "La calificacion se encuentra en un rango invalido " << endl;
		return ERROR;
	}

	Tupla<nat,nat> tupla = ObtenerPosiciones(idAtraccion);

	//Ya no esta ingresado ese codigo de Atraccion
	if(this->matrizAtraccion[tupla.Dato1][tupla.Dato2] == NULL)
	{
		cout << "No se encontro la atraccion con codigo: " << idAtraccion << endl;
		return ERROR;
	}
	
	//Si rango es valido y existe atraccion, inserto.
		
	Tupla<nat, int, Cadena> cal = Tupla<nat, int, Cadena>(fechaCalificacion, calificacion, comentarios);
	Puntero<Atraccion> atraccion = this->matrizAtraccion[tupla.Dato1][tupla.Dato2];
		
	atraccion->AgregarOActualizarCalificacion(cal);

	//actualizo el promedio de la ciudad
	Puntero<Ciudad> ciudadActualizar = atraccion->ObtenerCiudad(); 
	ciudadActualizar->ActualizarPromedio(calificacion);
	
	//Si cantAtraccionesE de la ciudad es 1, entonces lo agrego al heap
	if (ciudadActualizar->ObtenerCantAtracciones() == 1)
	{
		heapCiudades->Insertar(ciudadActualizar);
	}
	else
	{
		heapCiudades->ActualizarPrioridad(ciudadActualizar);
	}


	return OK;
	

}

TipoRetorno Sistema::AltaConexion(Cadena ciudadOrigen, Cadena ciudadDestino, nat distancia, nat omnibus, nat aviones)
{
	//costo = distancia * costo_transporte_por_km,

	//Controlo que existan las cuidades
	if (!this->tablaCiudades->EstaDefinido(ciudadOrigen)){
		cout << "No existe una ciudad con nombre: " << ciudadOrigen << endl;
		return ERROR;			
	}
	
	if (!this->tablaCiudades->EstaDefinido(ciudadDestino)){
		cout << "No existe una ciudad con nombre: " << ciudadDestino << endl;
		return ERROR;			
	}
	
	if(distancia<=0){
		cout<< "Distancia no puede ser 0"<<endl;
		return ERROR;
	}
	
	//Alguna de las cantidades es negativa. Retorno Error
	if(aviones < 0 || omnibus <0){
		cout<<"Cantidad de aviones o omnibus invalida" <<endl;
		return ERROR;
	}
	
	//Ambas cantidades son 0. Retorno error
	if(aviones == 0 && omnibus == 0){
		cout<<"Alguna de las cantidades tiene que ser mayor que 0" <<endl;
		return ERROR;
	}
	
	// recupero las cuidades por nombre (clave) para insertarlos como vertice en el grafo
	Puntero<Ciudad> ciudadA = this->tablaCiudades->Recuperar(ciudadOrigen);
	Puntero<Ciudad> ciudadB = this->tablaCiudades->Recuperar(ciudadDestino);
	
	// inserto las cuidades como vertices en el grafo
	if(!this->grafoCiudad->ExisteVertice(ciudadA)){
		this->grafoCiudad->AgregarVertice(ciudadA);
	}
	if(!this->grafoCiudad->ExisteVertice(ciudadB)){
		this->grafoCiudad->AgregarVertice(ciudadB);
	}
	
	Puntero<Conexion> conexion = new Conexion();
	conexion->distancia = distancia;
	conexion->aviones = aviones;
	conexion->omnibus = omnibus;
	
	//Chequear que esto no vuele!
	conexion->costoOmnibus = distancia*this->costoOmnibus;
	conexion->costoAvion = distancia*this->costoAvion;

	this->grafoCiudad->AgregarArco(ciudadA, ciudadB, conexion);
	
	//A la ciudad origen le agrego la ciudad destino. Se ordena por nombre dentro del avl de ciudad
	ciudadA->AgregarCiudadAdyacente(ciudadB);
	
	//Genero la matriz de WARSHALL para saber si hay camino
	grafoCiudad->ActualizarMatrizCaminos();

	//Codigo de test
	/*Iterador<pCiudad> ady = ciudadA->ObtenerAdyacentes();
	ady.Reiniciar();
	while(ady.HayElemento()){
		cout<<ady.ElementoActual()->ObtenerNombre()<<endl;
		ady.Avanzar();
	}

	Puntero<Conexion> laAux = this->grafoCiudad->ObtenerArcoAux(ciudadA, ciudadB);
	cout<<laAux->aviones;
	*/

	return OK;
}

Tupla<TipoRetorno, pAtraccion> Sistema::ConsultaAtraccion(Cadena idAtraccion)
{
	Tupla<nat,nat> tupla = ObtenerPosiciones(idAtraccion);

	//Ya no esta ingresado ese codigo de Atraccion
	if(this->matrizAtraccion[tupla.Dato1][tupla.Dato2] == NULL)
	{
		cout << "No se encontro la atraccion con codigo: " << idAtraccion << endl;
		return ERROR;
	}
	

	return Tupla<TipoRetorno, pAtraccion>(OK, this->matrizAtraccion[tupla.Dato1][tupla.Dato2]);

}

Tupla<TipoRetorno, Iterador<Tupla<Cadena, Cadena, TipoTransporte, nat>>> Sistema::Transportes()
{
	// No hay cuidades en el sistema
	if (this->cantCiudades==0){
		cout << "No existe ciudades en el sistema." << endl;
		return Tupla<TipoRetorno, Iterador<Tupla<Cadena, Cadena, TipoTransporte, nat>>>(ERROR, NULL);
	}
	
	else {

		Puntero<ILista<Tupla<Cadena, Cadena, TipoTransporte, nat>>> lista = new ListaSimple<Tupla<Cadena, Cadena, TipoTransporte, nat>>();

		Iterador<pCiudad> iter = avlCiudades->ObtenerIterador();
		while (iter.HayElemento()){
			//Tengo la primer ciudad del sistema
			Puntero<Ciudad> ciudad = (Puntero<Ciudad>) iter.ElementoActual();
			//Obtengo sus adyacentes ordenados por nombre del avl interno de ciudad
			Iterador<pCiudad> misCiudadesAdyacentes = ciudad->ObtenerAdyacentes();
			while(misCiudadesAdyacentes.HayElemento())
			{
				Puntero<Ciudad> ciudadAdyacente = (Puntero<Ciudad>) misCiudadesAdyacentes.ElementoActual();
				Puntero<Conexion> conexion = this->grafoCiudad->ObtenerArcoAux(ciudad, ciudadAdyacente);
				if(conexion != NULL){
					nat cantOmnibus = conexion->omnibus;
					nat cantAviones = conexion->aviones;

					Tupla<Cadena, Cadena, TipoTransporte, nat> tuplaBondi;
					Tupla<Cadena, Cadena, TipoTransporte, nat> tuplaAvion;
					
					tuplaBondi = Tupla<Cadena, Cadena, TipoTransporte, nat>(ciudad->ObtenerNombre(), ciudadAdyacente->ObtenerNombre(), Omnibus, cantOmnibus);
					tuplaAvion = Tupla<Cadena, Cadena, TipoTransporte, nat>(ciudad->ObtenerNombre(), ciudadAdyacente->ObtenerNombre(), Avion, cantAviones);

					//Si la cantidad no es 0, lo agrego
					if(cantAviones !=0){
						lista->InsertarAlFinal(tuplaAvion);
					}
					if(cantOmnibus !=0){
						lista->InsertarAlFinal(tuplaBondi);
					}
				}

				misCiudadesAdyacentes.Avanzar();
			}
			iter.Avanzar();
		}
		
		//Codigo de test
		/*Iterador<Tupla<Cadena, Cadena, TipoTransporte, nat>> iterMio = lista->ObtenerIterador();
		
		while(iterMio.HayElemento())
		{
			Tupla<Cadena, Cadena, TipoTransporte, nat> amigo = iterMio.ElementoActual();
			cout<<"Origen: "<<amigo.ObtenerDato1();
			cout<<"Destingo: "<<amigo.ObtenerDato2();
			cout<<"TipoT: "<<amigo.ObtenerDato3();
			cout<<"Cantidad: "<<amigo.ObtenerDato4()<<endl;
			iterMio.Avanzar();
		}*/

		return Tupla<TipoRetorno, Iterador<Tupla<Cadena, Cadena, TipoTransporte, nat>>>(OK, lista->ObtenerIterador());
	}
}


// Entrega final Tipo 1

Tupla<TipoRetorno , pCiudad> Sistema::CiudadMejoresAtracciones()
{
	Puntero<Ciudad> pCiudadRetorno = heapCiudades->Minimo();

	return Tupla<TipoRetorno, pCiudad>(OK, pCiudadRetorno);
}

TipoRetorno Sistema::IngresoExcursionItinerario(nat nroExcursion, nat fecha, Iterador<Cadena> itinerario)
{
	//duración = distancia / velocidad_transporte_por_km,
	//costo = distancia * costo_transporte_por_km

	if (this->tablaExcursion->EstaDefinido(nroExcursion)){
		cout << "Ya existe ese nro de Excursion: " << nroExcursion << endl;
		return ERROR;			
	}
	// Sobrepasa la cantidad maxima de excursiones
	if(this->cantExcursiones == this->maxExcursiones){
		return ERROR;
	}


	Puntero<ListaSimple<Puntero<Ciudad>>> lista = new ListaSimple<Puntero<Ciudad>>();
	while(itinerario.HayElemento())
		{
			Cadena ciudadActual = itinerario.ElementoActual();
			Puntero<ICiudad> ciudadAc = this->tablaCiudades->Recuperar(ciudadActual);
				
			if(!this->grafoCiudad->ExisteVertice(ciudadAc)){
				cout << "La ciudad esta aislada." << endl;
				return ERROR;
			}
			
			//Alguna de las cuidades no esta en el sistema
			if (!this->tablaCiudades->EstaDefinido(ciudadActual)){
				
				return ERROR;
			}
			//Existe la ciudad, continuo
			else{
				lista->InsertarAlFinal(ciudadAc);
		}
			
			itinerario.Avanzar();
	}


	
	this->cantExcursiones++;

	return OK;

}

TipoRetorno Sistema::IngresoExcursionDistancia(nat nroExcursion, nat fecha, nat tiempoMax, Cadena ciudadOrigen, Cadena ciudadDestino, Iterador<Cadena> ciudadesNoDeseadas)
{
	//Controlo que existan las cuidades
	if (!this->tablaCiudades->EstaDefinido(ciudadOrigen)){
		cout << "No existe una ciudad con nombre: " << ciudadOrigen << endl;
		return ERROR;			
	}
	
	if (!this->tablaCiudades->EstaDefinido(ciudadDestino)){
		cout << "No existe una ciudad con nombre: " << ciudadDestino << endl;
		return ERROR;			
	}

	//Existe excursion
	if (this->tablaExcursion->EstaDefinido(nroExcursion)){
		cout << "Ya existe ese nro de Excursion: " << nroExcursion << endl;
		return ERROR;			
	}

	// Sobrepasa la cantidad maxima de excursiones
	if(this->cantExcursiones == this->maxExcursiones){
		return ERROR;
	}


	return NO_IMPLEMENTADA;
}

TipoRetorno Sistema::IngresoExcursionCosto(nat nroExcursion, nat fecha, Cadena ciudadOrigen, Cadena ciudadDestino)
{
	//duración = distancia / velocidad_transporte_por_km,

	//Controlo que existan las cuidades
	if (!this->tablaCiudades->EstaDefinido(ciudadOrigen)){
		cout << "No existe una ciudad con nombre: " << ciudadOrigen << endl;
		return ERROR;			
	}
	
	if (!this->tablaCiudades->EstaDefinido(ciudadDestino)){
		cout << "No existe una ciudad con nombre: " << ciudadDestino << endl;
		return ERROR;			
	}

	//Existe excursion
	if (this->tablaExcursion->EstaDefinido(nroExcursion)){
		cout << "Ya existe ese nro de Excursion: " << nroExcursion << endl;
		return ERROR;			
	}

	// Sobrepasa la cantidad maxima de excursiones
	if(this->cantExcursiones == this->maxExcursiones){
		return ERROR;
	}

	// recupero las ciudades
	Puntero<ICiudad> ciudadOri = this->tablaCiudades->Recuperar(ciudadOrigen);
	Puntero<ICiudad> ciudadDes = this->tablaCiudades->Recuperar(ciudadDestino);

	if(!this->grafoCiudad->ExisteVertice(ciudadOri) || !this->grafoCiudad->ExisteVertice(ciudadDes)){
		cout << "La ciudad esta aislada." << endl;
		return ERROR;
	}


	Puntero<Tupla<TipoRetorno, int, TipoTransporte, Iterador<pCiudad>>> pTupla = this->grafoCiudad->RecorridoMasBarato(ciudadOri,ciudadDes);
	
	if(pTupla->ObtenerDato1()==OK){
		//Excursion excursion = new Excursion(nroExcursion, fecha, pTupla->ObtenerDato2(), pTupla->ObtenerDato4());
		return	OK;	
	
	}else{

		return ERROR;
	}

	//Seguir aca
	
	
	return NO_IMPLEMENTADA;
}

Tupla<TipoRetorno, pExcursion> Sistema::ConsultaExcursion(nat nroExcursion)
{
	//Controlo que existan las cuidades
	if (!this->tablaExcursion->EstaDefinido(nroExcursion)){
		cout << "No existe una excursion con ese id" << endl;
		return Tupla<TipoRetorno, pExcursion>(ERROR, NULL);			
	}
	
		
	return Tupla<TipoRetorno, pExcursion>(OK, this->tablaExcursion->Recuperar(nroExcursion));
}

Tupla<TipoRetorno, Iterador<pCiudad>> Sistema::ListadoCiudades()
{
	return Tupla<TipoRetorno, Iterador<pCiudad>>(OK, avlCiudades->ObtenerIterador());
}

// Entrega final Tipo 2

Tupla<TipoRetorno, bool> Sistema::HayCamino(Cadena ciudadOrigen, Cadena ciudadDestino)
{

	bool existeCamino = false;
	
	if(tablaCiudades->EstaDefinido(ciudadOrigen) && tablaCiudades->EstaDefinido(ciudadDestino))
	{
		Puntero<Ciudad> ciudadO = tablaCiudades->Recuperar(ciudadOrigen);
		Puntero<Ciudad> ciudadD = tablaCiudades->Recuperar(ciudadDestino);
		return Tupla<TipoRetorno, bool>(OK, grafoCiudad->ExisteCamino(ciudadO,ciudadD));
		
	}
	else
	{
		return Tupla<TipoRetorno, bool>(ERROR, NULL);
	}

	

	//return Tupla<TipoRetorno, bool>(NO_IMPLEMENTADA, NULL);
}

Tupla<TipoRetorno, Iterador<pAtraccion>> Sistema::AtraccionesAVisitarA(nat nroExcursion, nat dineroMax)
{
	return Tupla<TipoRetorno, Iterador<pAtraccion>>(NO_IMPLEMENTADA, NULL);
}

Tupla<TipoRetorno, Iterador<pAtraccion>> Sistema::AtraccionesAVisitarB(nat nroExcursion, nat dineroMax)
{
	return Tupla<TipoRetorno, Iterador<pAtraccion>>(NO_IMPLEMENTADA, NULL);
}

Tupla<TipoRetorno, nat> Sistema::MaximosViajantes(Cadena ciudadOrigen, Cadena ciudadDestino)
{
	return Tupla<TipoRetorno, nat>(NO_IMPLEMENTADA, 0);
}

// Operaciones opcionales

Tupla<TipoRetorno, Iterador<pCiudad>> Sistema::CiudadesEsenciales(Cadena ciudadOrigen, Cadena ciudadDestino, TipoTransporte tipoTransporte)
{
	return Tupla<TipoRetorno, Iterador<pCiudad>>(NO_IMPLEMENTADA, NULL);
}

Tupla<nat, nat> Sistema::ObtenerPosiciones(Cadena identificador)
{
	nat largo;
	nat ancho;
	Cadena identificadorLetra = identificador.SubCadena(0,1);
	Cadena cadAncho = identificador.SubCadena(1,2);

	int n1 = identificador[1] - '0';
	int n2 = identificador[2] - '0';
	ancho = (n1 * 10) + n2;

	int nLargo = identificador[0] - '0';
	largo = nLargo - 17;

	Tupla <nat, nat> retorno = Tupla<nat, nat>(largo,ancho);
	
	return retorno;	


	
}

